<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// $Id: Model.class.php 2815 2012-03-13 07:08:56Z liu21st $

/**
 +------------------------------------------------------------------------------
 * Newzan Model模型扩展类
 * 加强错误验证机制
 +------------------------------------------------------------------------------
 * @category   Think
 * @package  Think
 * @subpackage  Core
 * @author    liu21st <liu21st@gmail.com>
 * @version   $Id: Model.class.php 2815 2012-03-13 07:08:56Z liu21st $
 +------------------------------------------------------------------------------
 */
class NewzanModel extends Model {
    const MODEL_SELECT      =   1;      //  插入模型数据

	protected $_LANG_OPEN = true;

	/** @var RedisServer Redis实例. 注意: 不能直接使用! 需要调用$this->_getRedis()来调用.
	 * @see NewzanModel::_getRedis()  */
    protected $_redis = null;
    /** @var Cache */
    protected $_cache = null;
    
    /** @var taobaoAPI Object, instance for taobao api request */
    protected $_api = null;
        
	public function _initialize(){
        //import("@.Library.RedisServer");
        //$this->_redis = new RedisServer(REDIS_HOST);
        $this->_cache = Cache::getInstance('File');
        //init api reuest
        // init a api request , deleted , init api session in the taobaoaAPI _call method, will make the model init faster.
        import("@.Library.TaobaoAPI.taobaoAPI");
        $this->_api = taobaoAPI::init();
	}
	
	/* set the api access token by manual
	 * 例如脚本后台执行时,没有session但是需要用access_token,
	 * 此时可以手动set access token,这样$this->_ap就可以执行了 
	 * @param string access_token
	 * @return void
	 * 
	 * */
	public function setAccessToken($access_token){
	    $this->_api->setAccessToken($access_token);
	}
	
	
    protected function _after_select(&$result , $opts){
    	if(isset($opts['assoc_key']) && $opts['assoc_key']) { // 将自然数组变为表主键为key的associated数组. 并且将数组键值转化为float型.
	        $pk   =  $this->getPk();
	        $new_result = array();
	        foreach($result as $r){
	            if( !isset($r[$pk]) ) break;
	            $new_result[ (string) $r[$pk]] = $r;
	        }
	        $result = $new_result;
    	}
    }
    
    /**
     * 将给定条件的数据复制/转移到给定的目标表中.
     * @param array|string $where 符合ThinkPHP中Model的查询条件.
     * @param string $targetTbl 要复制/转移数据到的目标表. 如果可能, 最好也给定数据库名, 已防止跨库操作失败.
     * @param bool $delInCurrTbl = false 是否同时将符合条件的数据从本表删除. 如果为true, 相当于转移数据, 否则为复制数据.
     * @return int 返回复制/转移的记录数.
     */
    public function insertSelect($where, $targetTbl, $delInCurrTbl = false) {
    	$columns = join(',', $this->getDbFields());
    	$selectSql = $this->field($columns)->where($where)->buildSql();
    	$sql = "INSERT " . ($ignoreOnDup ? 'IGNORE' : '') . "INTO $targetTbl ($columns) $selectSql";
    	$num = $this->execute($sql);
    	if($delInCurrTbl)
    		$this->where($where)->delete();
    	return $num;
    }
    
    /**
     * 批量插入数据, 如果遇到重复主键则更新该记录.
     * 
     * @param array(2D) $dataList 数据列表 array( array(k=>v) ). 
     * @param array $options sql选项
     * @param array(string) [可选]$excludedUpdateFields 重复主键时不更新的字段. 默认为主键($this->pk).
     * @return false|int
     */
    public function addAllOnDupUpdate($dataList, $options = array(), $excludedUpdateFields = array()) {
    	if(empty($dataList)) {
    		$this->error = L('_DATA_TYPE_INVALID_');
    		return false;
    	}
    	if(!$excludedUpdateFields && isset($this->pk) && $this->pk) {
    		$excludedUpdateFields = is_array($this->pk) ? $this->pk : explode(',', $this->pk);
    	} 
    	
    	// 分析表达式
    	$options =  $this->_parseOptions($options);
    	// 数据处理
    	foreach ($dataList as $key=>$data){
    		$dataList[$key] = $this->_facade($data);
    	}
    	
    	if(!is_array($dataList[0])) return false;
    	$fields = array_keys($dataList[0]);
    	
    	//array_walk($fields, array($this, 'parseKey'));
    	$values  =  array();
    	foreach ($dataList as $data){
    		$value   =  array();
    		foreach ($data as $key=>$val){
    			$val   =  $this->parseValue($val);
    			if(is_scalar($val)) { // 过滤非标量数据
    				$value[]   =  $val;
    			}
    		}
    		$values[]    = '('.implode(',', $value).')';
    	}
    	$sqlOnDup = ' ON DUPLICATE KEY UPDATE ';
    	foreach($fields as $field) {
    		if(!in_array($field, $excludedUpdateFields))
    			$sqlOnDup .= " $field=VALUES($field),";
    	}
    	$sqlOnDup = substr($sqlOnDup, 0, - 1);
    	
    	$sql =  'INSERT INTO  ' . '`' . $options['table'] . '`'
    			. ' ('.implode(',', $fields).') VALUES ' . implode(',',$values)
    			. $sqlOnDup;
    	return $this->execute($sql);
    	 
    }


    /**
     * 批量插入数据, 如果遇到重复主键则更新该记录.
     *
     * @param array(2D) $dataList 数据列表 array( array(k=>v) ).
     * @param array $options sql选项
     * @param array(string) $excludedUpdateFields 重复主键时不更新的字段
     * @return false|int
     */
    public function addAllIgnoreDup($dataList, $options = array()) {
    	if(empty($dataList)) {
    		$this->error = L('_DATA_TYPE_INVALID_');
    		return false;
    	}
    	// 分析表达式
    	$options =  $this->_parseOptions($options);
    	// 数据处理
    	foreach ($dataList as $key=>$data){
    		$dataList[$key] = $this->_facade($data);
    	}
    	 
    	//if(!is_array($dataList[0])) return false; // TODO 这里只要是第一个元素即可, 无一定是 $dataList[0]
    	$first = current($dataList);
    	if(!is_array($dataList[0])) {
    		Log::record('Empty datalist: ' . join_2d('; ', ',', $dataList), Log::WARN);
    		return false;
    	}
    	
    	$fields = array_keys($first);
    	//array_walk($fields, array($this, 'parseKey'));
    	$values  =  array();
    	foreach ($dataList as $data){
    		$value   =  array();
    		foreach ($data as $key=>$val){
    			$val   =  $this->parseValue($val);
    			if(is_scalar($val)) { // 过滤非标量数据
    				$value[]   =  $val;
    			}
    		}
    		$values[]    = '('.implode(',', $value).')';
    	}

    	$sql =  'INSERT IGNORE INTO ' . ' `' . $options['table'] . '`'
    		. ' ('.implode(',', $fields).') VALUES ' . implode(',',$values);
    	 
//     	Log::record($sql, Log::SQL); // 不知道为啥Log::SQL级别的日志在生产环境下也会打印出来。
    	 
    	//     	echo $sql;
    	return $this->execute($sql);
    
    }
    
    /**
     * value分析
     * @access protected
     * @param mixed $value
     * @return string
     */
    protected function parseValue($value) {
    	if(is_string($value)) {
    		$value =  '\'' . addslashes($value) . '\'';
    	}elseif(isset($value[0]) && is_string($value[0]) && strtolower($value[0]) == 'exp'){
    		$value =  addslashes($value[1]);
    	}elseif(is_array($value)) {
    		$value =  array_map(array($this, 'parseValue'),$value);
    	}elseif(is_bool($value)){
    		$value =  $value ? '1' : '0';
    	}elseif(is_null($value)){
    		$value =  'null';
    	}
    	return $value;
    }
    
    /**
     * 获取redis实例. 按需创建, 防止多实例.
     * @return RedisServer 实例化过的Redis对象.
     */
	protected function _getRedis() {
		if(!$this->_redis) {
			$this->_redis = new RedisServer(REDIS_HOST);
			$this->_redis->Auth(REDIS_PASS);
		}
		return $this->_redis;
	}
	

	/**
	 * Log to global klogger defined in Scripts/common.php with level KLogger::Debug
	 * @param string $line Log message.
	 */
	protected function logDebug($line) {
		$this->_getKlog()->logDebug($line);
	}
	/**
	 * Log to global klogger defined in Scripts/common.php with level KLogger::Info
	 * @param string $line Log message.
	 */
	protected function logInfo($line) {
		$this->_getKlog()->logInfo($line);
	}
	
	public function api($class, $params) {
		return $this->_api->$class($params);
	}
	

	protected function _getKlog() {
		global $klog;
		if(!$klog) {
			import('@.Library.KLogger');
			$logLevel = ((defined('APP_DEBUG') && APP_DEBUG) ||(isset($_GET['test']) && $_GET['test'])) == 1 
					? KLogger::DEBUG 
				 	: defined('KLOG_LEVEL') ? KLOG_LEVEL : KLogger::INFO;
			$klog = new KLogger(SCRIPT_LOG_DIR . MODULE_NAME . 'Action-' . ACTION_NAME, $logLevel);
		}
		return $klog;
	}
	

	/**
	 * 查询一个对象的log,并翻译成适合显示的结构
	 * @param int $user_id , user id
	 * @param string $id_field , 查询的id字段
	 * @param int|array(int) $ids , id值集合
	 * @param date_string $from， 查询历史起始时间， 2013-05-11 ，为空的时候查看最近7天之内历史记录
	 * @param date_string $to , 查询历史结束时间, 2013-06-13 , 为空查看最近7天之内历史记录
	 * @param bool $simple_disp 是否显示简单版说明
     * $param bool $count 是否返回数量而不是内容, default set is false
	 * @return mixed
	 * example array {
	 * 			 0 => array(
	 * 				'action' => KeywordLogActions::AUTO_NEW_KEYWORD,
	 * 				'updated_time' => 2013-06-19 12:23:23
	 * 				'action_name' => '自动开车加词',
	 * 				'action_explain' => '从2.36加价到2.42',
	 * 				'action_reason' => ''
	 * 			 )
	 * 			....
	 * 		}
	 */
	public function getLogs($user_id , $id_field , $ids  , $from = null, $to = null, $simple_disp = false, $count = false){
		//默认查询14天内的关键词调整历史
		$whr[$id_field] = array( 'IN' , $ids );
		if(!$from) {
			$from = date('Y-m-d H:i:s', date2time(-90) );
		}
		if($to) {
			$whr['updated_time'] = array('between', "$from , $to");
		} else {
			$whr['updated_time'] = array('egt', $from);
		}
		
		$tablename = $this->tableName . '_log';
		$logMdl = M($tablename);
		if($logMdl){
            if($count){
                $num = $logMdl->where( $whr )->count();
                return $num;
            }else{
                $logs = $logMdl->where( $whr )->order('id DESC')->select();
                $logs = $logs ? $logs : array();
                $detailed_logs = array();
                foreach($logs as $log){
                    $detailed_logs[] = $this->readLog($log, $simple_disp);
                }
                return $detailed_logs;			
            }
		}else{
			throw new NewzanException('不存在logs', RetStatus::INNER_ERR);
		}
	}


	/**
	 * 解释一条log的结果成可以显示的格式
	 * @param 数据库中的一条log记录， $log
	 * @return 解释后的log结果
	 */
	
	public function readLog($log, $simple_disp){
		$detailed = array(
				'action' => $log['action'],
				'keyword' => isset($log['keyword']) ? $log['keyword'] : null, 
				'action_name' => $this->action_names[$log['action']],
				'action_note' => json_decode( $log['note'] , true) ,
				'action_value' => $log['new_int_val'],
				'updated_time' => date('m-d H:i' , strtotime($log['updated_time']) ),
				'updated_date' => date('Y-m-d' , strtotime($log['updated_time']) ),
				'updated_stp' => $log['updated_time']
		);
	
		if($log['action'] < 10){
			$detailed['action_type'] = 'auto';
		}else{
			$detailed['action_type'] = 'manual';
		}
	
		//具体动作解释
		return $detailed;
	}
	
	
}
